package com.qf.user.manager.filter;

import com.qf.common.model.QfUser;
import com.qf.common.utils.JsonUtil;
import com.qf.common.utils.JwtUtil;
import com.qf.user.manager.bean.LoginUser;
import com.qf.user.manager.service.UserService;
import io.jsonwebtoken.Claims;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;

/**
 * @author 千锋健哥
 */
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

    @Value("${jwt.token-header}")
    private String header;

    @Autowired
    private UserService userService;

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain chain) throws ServletException, IOException {
        //1. 获取访问的url地址
        String path = request.getRequestURI();

        //2. 判断url地址如果是/user/auth那么是登录路径, 则无条件放行
        if (path.contains("/user/auth")) {
            chain.doFilter(request, response);
            return;
        }

        //3. 从请求头中获取jwt
        String token = request.getHeader(header);


        //4. 判断jwt不为空
        if (!StringUtils.isEmpty(token)) {
            String userName = null;
            try {
                //5. 解析jwt
                Claims claims = JwtUtil.parseJWT(token);
                //获取解析后jwt中的用户名
                userName = claims.getSubject();
            } catch (Exception e) {
                writeJSON(request, response, new AccessDeniedException("非法token,不允许访问!"));
                return;
            }

            //6. 根据用户名到数据库中获取用户对象
            QfUser qfUser = userService.getUserByUserName(userName);

            //7. 根据解析后的用户名, 到数据库中获取对应的权限集合
            List<String> menuList = userService.findUserMenuByUserName(userName);
            //封装springSecurity的权限集合对象
            List<SimpleGrantedAuthority> authList = new ArrayList<>();
            for (String menu : menuList) {
                authList.add(new SimpleGrantedAuthority(menu));
            }
            //7. 将用户, 权限封装成SpringSecurity的UserDetails对象返回给SpringSecurity
            LoginUser loginUser = new LoginUser(qfUser, authList);

            //将SpringSecurity的登录实体对象, 封装到SpringSecurity的用户名, 密码认证对象中
            UsernamePasswordAuthenticationToken authToken =
                    new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
            //将封装后的交给SpringSecurity核心组件
            SecurityContextHolder.getContext().setAuthentication(authToken);
            //8. 放行
            chain.doFilter(request, response);
        }
    }


    /**
     * json处理
     * @param request
     * @param response
     * @param obj
     * @throws IOException
     * @throws ServletException
     */
    private void writeJSON(HttpServletRequest request,
                           HttpServletResponse response,
                           Object obj) throws IOException, ServletException {
        //这里很重要，否则页面获取不到正常的JSON数据集
        response.setContentType("application/json;charset=UTF-8");

        //跨域设置
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Access-Control-Allow-Method", "POST,GET");
        //输出JSON
        PrintWriter out = response.getWriter();
        out.write(JsonUtil.toJsonString(obj));
        out.flush();
        out.close();
    }

}
